#include <stdio.h>
#include <string.h>

#include "ysh.h"
#include "parse.h"
#include "la.h"
#include "error.h"
#include "jc.h"
#include "pool.h"

#define MIN(x, y) ((x)<(y)?(x):(y))
#define IN_REDIRECTION 0
#define OUT_REDIRECTION 1

CommandLine current_cmdline;

static void prog_invocation(void);
static void redirection(int d);
static void i_exit(void);
static void i_jobs(void);
static void i_echo(void);
static void i_fg(void);
static void i_bg(void);
static void i_history(void);
static void i_cd(void);
static void i_exec(void);
static Bool match_read(TokenType tt);

static void init(void)
{
    // if error was found during the parsing process, it 
    // will be set to FALSE.
    memset(&current_cmdline, (char)0, sizeof(CommandLine));
    current_cmdline.is_valid = TRUE;
    current_cmdline.run_in_bg = FALSE;
    current_cmdline.in_redirect = FALSE;
    current_cmdline.out_redirect = FALSE;

    with_error = FALSE;
}

void cat_to_raw(char *s, int size)
{
    // Total length of the raw string should be less than or 
    // equal with LINE_BUF_SIZE - 1.
    int added_size = MIN(size, LINE_BUF_SIZE - current_cmdline.raw_string_len - 1);
    strncat(current_cmdline.raw_string, s, added_size);
    current_cmdline.raw_string_len += added_size;
}

static void copy_to_parsed(char *buf)
{
    int pn;
    int *pan;
    char *pa;
    //strcpy(current_cmdline.parsed_cmdline[current_cmdline.pipe_num][current_cmdline.prog_args_num[current_cmdline.pipe_num]], token_buf);
    pn = current_cmdline.pipe_num;
    pan = &current_cmdline.prog_args_num[pn];
    pa = current_cmdline.parsed_cmdline[pn][*pan];
    strcpy(pa, buf);
    (*pan)++;
}

static Bool match_read(TokenType tt)
{
    return (read_token() == tt) ? TRUE : FALSE;
}

static void read_set_token(TokenType *tt, char *tb)
{
    read_token();
    (*tt) = current_token;
    if (tb != NULL) {
        strcpy(tb, token_buf);
    }
}

static void cmdline_parse()
{
    TokenType t_tt;
    char t_tb[TOKEN_BUF_SIZE] = {0};
    read_set_token(&t_tt, t_tb);
    switch (t_tt) {
        case TT_STRING:
            copy_to_parsed(t_tb);
            prog_invocation();
            break;
        case TT_IEXIT:
            i_exit();
            break;
        case TT_IJOBS:
            i_jobs();
            break;
        case TT_IECHO:
            i_echo();
            break;
        case TT_IFG:
            i_fg();
            break;
        case TT_IBG:
            i_bg();
            break;
        case TT_HISTORY:
            i_history();
            break;
        case TT_CD:
            i_cd();
            break;
        case TT_EXEC:
            i_exec();
            break;
        case TT_EOLTOKEN:
            break;
        default:
            error_unxtd_token(t_tb, "top-level parse"); 
            break;
    }
}

static void prog_invocation()
{
    TokenType t_tt;
    char t_tb[TOKEN_BUF_SIZE] = {0};

    do {
        read_set_token(&t_tt, t_tb);
        switch (t_tt) {
            case TT_STRING:
                copy_to_parsed(t_tb);
                break;
            case TT_INREDIRECT:
                if (current_cmdline.in_redirect == FALSE && current_cmdline.pipe_num == 0) {
                    redirection(IN_REDIRECTION);
                }
                else {
                    if (current_cmdline.pipe_num != 0) {
                        error_msg("Input redirection should be mounted on the first program invocation.\n");
                    }
                    else if (current_cmdline.in_redirect == TRUE) {
                        error_msg("Input redirection already found before, no more is expected.\n");
                    }
                }
                break;
            case TT_OUTREDIRECT:
                if (current_cmdline.out_redirect == FALSE) {
                    redirection(OUT_REDIRECTION);
                }
                else {
                    error_msg("No repeat of output redirection.\n");
                }
                break;
            case TT_PIPE:
                if (current_cmdline.out_redirect == TRUE) {
                    error_msg("Output redirection was found before, no extra pipe was expected here.\n");
                }
                else if (current_cmdline.prog_args_num[current_cmdline.pipe_num] == 0) {
                    error_msg("'... | <ProgInvocation> | ...', <ProgInvocation> cannot be NULL.\n");
                }
                current_cmdline.pipe_num++;
                break;
            case TT_BACKGROUND:
                if (current_cmdline.run_in_bg == FALSE) {
                    current_cmdline.run_in_bg = TRUE;
                }
                else {
                    error_msg("Token '&' has been found before. No repeat.\n");
                }
                break;
            case TT_EOLTOKEN:
                if (current_cmdline.prog_args_num[current_cmdline.pipe_num] == 0) {
                    error_msg("... | EOL is not allowed.");
                }
                break;
            default:
                error_unxtd_token(t_tb, "<ProgInvocation>");
        }
    } while (t_tt != TT_EOLTOKEN && with_error == FALSE);
    if (with_error == FALSE) {
        jc_start_job();
    }
}

static void redirection(int d)
{
    TokenType t_tt;
    char t_tb[TOKEN_BUF_SIZE] = {0};

    read_set_token(&t_tt, t_tb);
    switch (d) {
        case IN_REDIRECTION:
            // Check existence.
            current_cmdline.in_redirect = TRUE;
            strcpy(current_cmdline.in_redirect_path, t_tb);
            break;
        case OUT_REDIRECTION:
            current_cmdline.out_redirect = TRUE;
            strcpy(current_cmdline.out_redirect_path, t_tb);
            break;
        default:
            sprintf(error_message, "Expecting redirection file's path, but \'%s\' was encountered.\n", t_tb);
            error_msg(error_message);
            break;
    }
}

static void i_exit(void)
{
    if (match_read(TT_EOLTOKEN) == TRUE) {
        jc_exit();
    }
    else {
        error_msg("EOL was expected at the end of command line string.\n");
    }
}

static void i_jobs(void)
{
    if (match_read(TT_EOLTOKEN) == TRUE) {
        jc_jobs();
    }
    else {
        error_msg("EOL was expected at the end of command line string.\n");
    }
}

static void i_echo(void)
{
    TokenType t_tt;
    char t_tb[TOKEN_BUF_SIZE] = {0};
    do {
        read_string(&t_tt, t_tb);
        if (t_tt == TT_ECHOSTATUS) {
            sprintf(t_tb, "%d", jc_status);
        }
        copy_to_parsed(t_tb);
    } while (t_tt != TT_EOLTOKEN);
    jc_echo();
}

static void i_fg(void)
{
    TokenType t_tt;
    char t_tb[TOKEN_BUF_SIZE] = {0};

    read_string(&t_tt, t_tb);
    if (t_tt == TT_PERCENTINT) {
        if (match_read(TT_EOLTOKEN) == TRUE) {
            jc_fg(t_tt, t_tb);
        }
        else {
            error_msg("EOL was expected at the end of command line string.\n");
        }
    }
    else {
        error_msg("TT_PERCENTINT was expected after internal command \'fg\'\n");
    }
}

static void i_bg(void)
{
    TokenType t_tt;
    char t_tb[TOKEN_BUF_SIZE] = {0};

    read_string(&t_tt, t_tb);
    if (t_tt == TT_PERCENTINT) {
        if (match_read(TT_EOLTOKEN) == TRUE) {
            jc_bg(t_tt, t_tb);
        }
        else {
            error_msg("EOL was expected at the end of command line string.\n");
        }
    }
    else {
        error_msg("TT_PERCENTINT was expected after internal command \'bg\'\n");
    }
}

static void i_history(void)
{
    if (read_token() != TT_EOLTOKEN) {
        error_msg("EOL was expected after internal command 'history'.\n"); 
    }
    jc_history();
}

static void i_cd(void)
{
    TokenType t_tt;
    char t_tb[TOKEN_BUF_SIZE] = {0};
    read_string(&t_tt, t_tb);
    if (t_tt == TT_STRING) {
        copy_to_parsed(t_tb);
        if (match_read(TT_EOLTOKEN) == TRUE) {
            jc_cd();
        }
        else {
            error_msg("EOL was expected after internal command 'cd'.\n"); 
        }
    }
    else if (t_tt == TT_EOLTOKEN) {
        copy_to_parsed("");
        jc_cd();
    }
    else {
        error_msg("TT_STRING was expected after internal command 'cd'.\n"); 
    }
}

static void i_exec(void)
{
    TokenType t_tt;
    char t_tb[TOKEN_BUF_SIZE] = {0};
    do {
        read_string(&t_tt, t_tb);
        copy_to_parsed(t_tb);
    } while (t_tt != TT_EOLTOKEN);
    current_cmdline.prog_args_num[0]--;
    jc_exec();
}

void parse(void)
{
    init();
    cmdline_parse();
}


